//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace WorkflowApplicationControls
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using WorkflowApplication.Interfaces;
    using System.Windows.Controls;
    using Microsoft.Practices.Unity;
    using SR = Microsoft.Research.DataLayer;
    using System.Collections.ObjectModel;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using System.Windows;
    using System.Windows.Input;
    using System.ComponentModel;
    using Microsoft.Research.eResearch.Common.Linq;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using TridentAPI;
    using WorkflowApplicationModels;

    /// <summary>
    /// The presenter for the run control.
    /// </summary>
    public class RunControlPresenter : DependencyObject, ICenterPanePresenter, INotifyPropertyChanged
    {
        #region Private data

        /// <summary>
        /// The unity container instance.
        /// </summary>
        private IUnityContainer container;

        /// <summary>
        /// The workflow manager service instance.
        /// </summary>
        private IRegistryService registryService;

        /// <summary>
        /// The workflow name.
        /// </summary>
        private string workflowName;

        /// <summary>
        /// JobName.
        /// </summary>
        private string jobName;

        /// <summary>
        /// Notes corresponding to the job.
        /// </summary>
        private string jobNotes;

        /// <summary>
        /// The selected node.
        /// </summary>
        private MachineModel selectedNode;

        /// <summary>
        /// The selected user.
        /// </summary>
        private SR.User selectedUser;

        /// <summary>
        /// The current workflow model.
        /// </summary>
        private TridentWorkflowModel currentWorkflowModel;

        #endregion Private data

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="RunControlPresenter"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        /// <param name="view">The view.</param>
        /// <param name="service">The service.</param>
        public RunControlPresenter(IUnityContainer container, DetailsControl view, IRegistryService service)
        {
            this.container = container;
            this.ContentView = view;
            this.registryService = service;

            this.ContentView.DataContext = this;
            this.WorkflowRequiredParameters = new ObservableCollection<ParameterDescriptionModel>();
            this.NodeList = new ObservableCollection<MachineModel>();
            this.UserList = new ObservableCollection<SR.User>();
            this.AdditionalParameters = new ObservableCollection<AdditionalParametersModel>();

            // Initialize the commands.
            this.CreateJobCommand = new DelegateCommand<object[]>(this.ExecuteJobCreateCommand);
            this.AddAdditionalParameterCommand = new DelegateCommand<object>(p => AddAdditionalParametersExecute());
            this.DeleteAdditionalParameterCommand = new DelegateCommand<AdditionalParametersModel>(this.DeleteAdditionalParametersExecute, this.DeleteAdditionalParamtersCanExecute);

            // Initialize the type provider helper.
            TypeProviderHelper.Initialize(this.registryService.CurrentConnection);

            SR.ExternalRegistryHelperProxy.Initialize(this.registryService.CurrentConnection);
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// The command which creates a Job in the registry.
        /// </summary>
        public ICommand CreateJobCommand { get; set; }

        /// <summary>
        /// Command to add a additional parameter.
        /// </summary>
        public ICommand AddAdditionalParameterCommand { get; set; }

        /// <summary>
        /// Command to remove an additional parameter.
        /// </summary>
        public DelegateCommand<AdditionalParametersModel> DeleteAdditionalParameterCommand { get; set; }

        /// <summary>
        /// The workflow name.
        /// </summary>
        public string WorkflowName
        {
            get
            {
                return this.workflowName;
            }
            set
            {
                this.workflowName = value;
                this.OnNotifyPropertyChanged("WorkflowName");
            }
        }

        /// <summary>
        /// The job name.
        /// </summary>
        public string JobName
        {
            get
            {
                return this.jobName;
            }
            set
            {
                this.jobName = value;
                this.OnNotifyPropertyChanged("JobName");
            }
        }

        /// <summary>
        /// The workflow required parameters.
        /// </summary>
        public ObservableCollection<ParameterDescriptionModel> WorkflowRequiredParameters { get; private set; }

        /// <summary>
        /// The list of nodes in the registry.
        /// </summary>
        public ObservableCollection<MachineModel> NodeList { get; private set; }

        /// <summary>
        /// The list of users.
        /// </summary>
        public ObservableCollection<SR.User> UserList { get; private set; }

        /// <summary>
        /// Gets the additional parameters collection.
        /// </summary>
        public ObservableCollection<AdditionalParametersModel> AdditionalParameters { get; private set; }

        /// <summary>
        /// The model of the workflow to be displayed in the window.
        /// </summary>
        public TridentWorkflowModel CurrentWorkflowModel
        {
            get
            {
                return this.currentWorkflowModel;
            }
            set
            {
                this.currentWorkflowModel = value;
                this.AssignSelection();
                this.OnNotifyPropertyChanged("CurrentWorkflowModel");
            }
        }

        /// <summary>
        /// The selected machine in the combobox.
        /// </summary>
        public MachineModel SelectedMachine
        {
            get
            {
                return this.selectedNode;
            }
            set
            {
                this.selectedNode = value;
                this.OnNotifyPropertyChanged("SelectedMachine");
            }
        }

        /// <summary>
        /// The selected machine in the combobox.
        /// </summary>
        public SR.User SelectedUser
        {
            get
            {
                return this.selectedUser;
            }
            set
            {
                this.selectedUser = value;
                this.OnNotifyPropertyChanged("SelectedUser");
            }
        }

        /// <summary>
        /// Gets or sets the notes corresponding to the job.
        /// </summary>
        public string JobNotes
        {
            get
            {
                return this.jobNotes;
            }
            set
            {
                this.jobNotes = value;
                this.OnNotifyPropertyChanged("JobNotes");
            }
        }

        /// <summary>
        /// Flag to check if all the properties have valid values in the property pane
        /// </summary>
        public static readonly DependencyProperty PropertiesValidProperty = DependencyProperty.Register("PropertiesValid", typeof(Boolean), typeof(RunControlPresenter), new PropertyMetadata(true));

        /// <summary>
        /// Flag to check if all the properties have valid values in the property pane.
        /// </summary>
        public bool PropertiesValid
        {
            get { return (bool)GetValue(PropertiesValidProperty); }
            set { SetValue(PropertiesValidProperty, value); }
        }

        #endregion Properties

        #region Private Methods

        /// <summary>
        /// Assigns the selection.
        /// </summary>
        /// <param name="selectedId">The selected id.</param>
        private void AssignSelection()
        {
            this.ClearContent();
            this.CurrentWorkflowModel.GetWorkflowRequiredParameters().ForEach(item => this.WorkflowRequiredParameters.Add(item));
            this.WorkflowName = this.CurrentWorkflowModel.Name;

            this.UpdateNodeList();
            this.GetUserList();

            // If it having only one node select the node by default
            if (this.NodeList.Count >= 2)
            {
                this.SelectedMachine = this.NodeList[1];
            }
            else if (this.NodeList.Count >= 1)
            {
                this.SelectedMachine = this.NodeList[0];
            }

            // Set the current user as the default scheduler.
            this.SelectDefaultUser();
        }

        /// <summary>
        /// Clears the content.
        /// </summary>
        private void ClearContent()
        {
            this.WorkflowRequiredParameters.Clear();
            this.NodeList.Clear();
            this.UserList.Clear();
            this.PropertiesValid = true;
            this.JobName = string.Empty;
            this.JobNotes = string.Empty;
            this.AdditionalParameters.Clear();
            this.DeleteAdditionalParameterCommand.RaiseCanExecuteChanged();
        }

        /// <summary>
        /// Retrieve the list of nodes present.
        /// </summary>
        private string UpdateNodeList()
        {
            string error = string.Empty;
            try
            {
                // Add default item.
                this.NodeList.Insert(0, new MachineModel(WorkflowApplicationResourceManager.GetString("SelectNodeText")));

                bool isInteractive = false;
                if (this.CurrentWorkflowModel != null)
                {
                    isInteractive = this.CurrentWorkflowModel.IsInteractive;
                }

                this.registryService.GetAllMachines(isInteractive).ForEach(item => this.NodeList.Add(item));
            }
            catch (SR.RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                error = ex.Message;
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                error = ex.Message;
            }

            return error;
        }

        /// <summary>
        /// Retrieve the list of Users present.
        /// </summary>
        private string GetUserList()
        {
            string error = string.Empty;
            try
            {
                if (TridentAuthentication.LoggedUserRole == UserRole.Guest)
                {
                    this.registryService.GetAllGuests().ForEach(item => this.UserList.Add(item));
                }
                else
                {
                    this.registryService.GetAllAuthenticatedUsers().ForEach(item => this.UserList.Add(item));
                }
            }
            catch (SR.RecordNotFoundException ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                error = ex.Message;
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                error = ex.Message;
            }

            return error;
        }

        /// <summary>
        /// Select the current user as the default user.
        /// </summary>
        private void SelectDefaultUser()
        {
            string userName = TridentAuthentication.LoggedUserInUserName;//Environment.UserName;
            if (this.UserList != null && this.UserList.Count > 0)
            {
                this.SelectedUser = this.UserList.FirstOrDefault(user => user.Name.Equals(userName, StringComparison.OrdinalIgnoreCase));
                if (this.SelectedUser == null)
                {
                    // If the current user is missing in the user list then select the first in the list.
                    this.SelectedUser = this.UserList[0];
                }
            }
        }

        /// <summary>
        /// Create a job in the registry.
        /// </summary>
        private void ExecuteJobCreateCommand(object[] parameters)
        {
            SR.Machine nodeSelected = null;
            if (this.selectedNode == null || !this.selectedNode.IsValid())
            {
                TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("ExecutionWithNoNodeSelected"));
                return;
            }
            else
            {
                try
                {
                    nodeSelected = SR.Machine.Load(this.selectedNode.Id, this.registryService.CurrentConnection);
                    if (nodeSelected.Availability == SR.ExecutorAvailabiltiy.Offline)
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("SelectedNodeOffline"));
                        return;
                    }
                }
                catch (Exception)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("SelectedNodeNotFound"));
                    return;
                }
            }

            try
            {
                // Check if all values are entered in the property pane.
                if (!this.PropertiesValid)
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("ReqPropertiesNotEnteredError"));
                    return;
                }

                if (!this.CheckIfAdditionalParametersValid())
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(WorkflowApplicationResourceManager.GetString("AdditionalParameterError"));
                    return;
                }

                JobDetailModel newJob = new JobDetailModel();
                newJob.WorkflowId = this.CurrentWorkflowModel.Id;
                newJob.JobName = this.JobName;
                newJob.Notes = this.JobNotes;

                // Add the additional parameters to the new job details.
                foreach (AdditionalParametersModel additionalParameter in this.AdditionalParameters)
                {
                    newJob.AdditionalParameters.Add(additionalParameter);
                }

                // Create the list of parameters and their values for the workflow.
                newJob.ParameterInfo = this.CurrentWorkflowModel.CreateWorkflowParameterInfo();

                // Create a job in registry.
                JobComposer wfClient = new JobComposer(this.registryService.CurrentConnection);
                SR.Job newRegJob = wfClient.ExecuteWFOnMachine(newJob, nodeSelected, this.selectedUser, false);
                if (parameters != null && parameters.Length > 0)
                {
                    JobWrapper jobWrapper = parameters[0] as JobWrapper;
                    if (jobWrapper != null)
                    {
                        jobWrapper.Job = newRegJob;
                    }
                }
            }
            catch (TridentCustomException ex)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
            }
        }

        /// <summary>
        /// Add an additional parameter to the list.
        /// </summary>
        private void AddAdditionalParametersExecute()
        {
            this.AdditionalParameters.Add(new AdditionalParametersModel());
            this.DeleteAdditionalParameterCommand.RaiseCanExecuteChanged();
        }

        /// <summary>
        /// Remove an additional parameter in the list.
        /// </summary>
        /// <param name="model">The additional parameter to delete.</param>
        private void DeleteAdditionalParametersExecute(AdditionalParametersModel model)
        {
            this.AdditionalParameters.Remove(model);
            this.DeleteAdditionalParameterCommand.RaiseCanExecuteChanged();
        }

        /// <summary>
        /// Checks if the delete addtional parameter button should be disabled.
        /// </summary>
        /// <returns>True if the button is to be disabled. False otherwise.</returns>
        private bool DeleteAdditionalParamtersCanExecute(AdditionalParametersModel model)
        {
            return !(this.AdditionalParameters.Count == 0);
        }

        /// <summary>
        /// Check if the additional parameters have valid values/ names.
        /// </summary>
        /// <returns>True if the additional parameters are valid. False otherwise.</returns>
        private bool CheckIfAdditionalParametersValid()
        {
            AdditionalParametersModel invalidModel = this.AdditionalParameters.FirstOrDefault(p => (string.IsNullOrEmpty(p.Parameter) || string.IsNullOrEmpty(p.ParameterValue)));
            return (invalidModel == null);
        }

        #endregion Private Methods

        #region ICenterPanePresenter Members

        /// <summary>
        /// Gets or sets the content view.
        /// </summary>
        /// <value>The content view.</value>
        public UserControl ContentView
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is initialized.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is initialized; otherwise, <c>false</c>.
        /// </value>
        public bool IsInitialized { get; set; }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called when the property changes.
        /// </summary>
        /// <param name="name">The name.</param>
        protected void OnNotifyPropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }
}
